#include "proccleanermodel.h"

#include <QDir>
#include <QStandardPaths>

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <stdio.h>

const QStringList NeteaseCloudMusicPkgNameList = {"netease-cloud-music",
                                                 "com.163.music"};

void ProcCleanerModel::initData()
{
    registerProcInfoMetaType();
    registerProcInfoListMetaType();

    m_netMonitorInter = new QDBusInterface(CCC_SYS_DAEMON_DBUS_NAME,
                                           CCC_SYS_DAEMON_DBUS_PATH,
                                           CCC_SYS_DAEMON_DBUS_IFC,
                                           QDBusConnection::systemBus(), this);
    loadCfgs();

    // connection
    m_netMonitorInter->connection().connect(CCC_SYS_DAEMON_DBUS_NAME,
                                            CCC_SYS_DAEMON_DBUS_PATH,
                                            CCC_SYS_DAEMON_DBUS_IFC,
                                            "KillProcFinished", this,  SLOT(onKillProcFinished(const QList<uint> &)));
}

bool ProcCleanerModel::loadCfgs()
{
    QFile file(CfgFilePath);
    if (!file.open(QIODevice::OpenModeFlag::ReadOnly)) {
        qWarning() << Q_FUNC_INFO << CfgFilePath << "open failed";
        file.close();
        return false;
    }

    QByteArray content = file.readAll();
    file.close();
    QJsonParseError err;
    QJsonDocument doc = QJsonDocument::fromJson(content, &err);
    if (err.error != QJsonParseError::NoError) {
        qCritical() << Q_FUNC_INFO << "parse json failed";
        return false;
    }

    QJsonObject mainJsonObj = doc.object();
    QJsonObject sysJsonObj = mainJsonObj.take(CFG_KEY_SYSTEM).toObject();

    m_memCleanPkgNameWhitelist.clear();
    QJsonArray whitelistJsonArray = sysJsonObj.value(CFG_KEY_MEM_CLEAN_APP_WHITELIST).toArray();
    for (QJsonArray::const_iterator cIter = whitelistJsonArray.begin();
         cIter != whitelistJsonArray.end(); cIter++) {
        m_memCleanPkgNameWhitelist.append(cIter->toString());
    }

    return true;
}

void ProcCleanerModel::saveCfgs()
{
    QFileInfo fInfo(CfgFilePath);
    if (!fInfo.dir().exists()) {
        fInfo.dir().mkdir(fInfo.dir().path());
    }

    // 先读取配置内容
    QJsonObject mainJsonObj;
    QFile file(CfgFilePath);
    if (file.open(QIODevice::OpenModeFlag::ReadOnly)) {
        QByteArray content = file.readAll();
        file.close();
        QJsonParseError err;
        QJsonDocument doc = QJsonDocument::fromJson(content, &err);
        if (err.error != QJsonParseError::NoError) {
            qWarning() << Q_FUNC_INFO << "parse json failed";
        }
        // 转成主json对象
        mainJsonObj = doc.object();
        file.close();
    }

    // 装载白名单列表
    QJsonArray whitelistJsonArray;
    for (QStringList::const_iterator cIter = m_memCleanPkgNameWhitelist.begin();
         cIter != m_memCleanPkgNameWhitelist.end(); cIter++) {
        whitelistJsonArray.append(*cIter);
    }
    // 装载系统配置
    QJsonObject sysJsonObj;
    sysJsonObj.insert(CFG_KEY_MEM_CLEAN_APP_WHITELIST, whitelistJsonArray);
    // 装载主体
    mainJsonObj.insert(CFG_KEY_SYSTEM, sysJsonObj);
    // 转成字节数组
    QJsonDocument changedDoc(mainJsonObj);
    QByteArray changedContent = changedDoc.toJson();

    if (!file.open(QIODevice::OpenModeFlag::WriteOnly)) {
        qWarning() << Q_FUNC_INFO << CfgFilePath << "open failed";
        file.close();
        return;
    }

    qint64 ret = file.write(changedContent);
    if (-1 == ret) {
        qWarning() << Q_FUNC_INFO << CfgFilePath << "write failed";
        file.close();
        return;
    }
    file.close();
}


void ProcCleanerModel::asyncKillProc(const QList<uint> &pids)
{
    QVariant arg = QVariant::fromValue<QList<uint>>(pids);
    m_netMonitorInter->call("AsyncKillProc", arg);
}

uint ProcCleanerModel::getPidByWid(ulong wid)
{
    Display *disp = XOpenDisplay(nullptr);
    Window win = wid;
    Atom xAtomPropName;
    Atom xAtomRetType;
    int retFormat;
    ulong retNitems;
    ulong retBytesAfter;
    uchar *retProp;
    uint pid = 0;

    xAtomPropName = XInternAtom(disp, "_NET_WM_PID", false);

    if (0 != XGetWindowProperty(disp, win, xAtomPropName, 0,
                           MAX_PROPERTY_VALUE_LEN / 4,
                           false, XA_CARDINAL, &xAtomRetType,
                           &retFormat, &retNitems, &retBytesAfter,
                           &retProp)) {
        qInfo() << Q_FUNC_INFO << "Cannot get %s _NET_WM_PID property of" << wid;
    } else {
        memcpy(&pid, retProp, 4);
    }

    XCloseDisplay(disp);
    return pid;
}

QWindow *ProcCleanerModel::getXWindowByPid(uint pid)
{
    QWindow *window = nullptr;
    for (quint32 wid : Dtk::Gui::DWindowManagerHelper::instance()->allWindowIdList()) {
        uint pidTmp = getPidByWid(wid);
        if (pid == pidTmp) {
            window = QWindow::fromWinId(wid);
            break;
        }
    }

    return window;
}

int ProcCleanerModel::killXWindow(const QWindow *w)
{
    Display *disp = XOpenDisplay(nullptr);
    int ret = XKillClient(disp, w->winId());
    XCloseDisplay(disp);

    return ret;
}

ProcCleanerModel::ProcCleanerModel(QObject *parent)
    : QObject(parent)
    , m_netMonitorInter(nullptr)
    , m_isPressingAppViewItem(false)
    , m_isMovingAppViewItem(false)
{
    initData();
}

ProcCleanerModel::~ProcCleanerModel()
{
}

ProcInfoList ProcCleanerModel::getProcInfoList() const
{
    QDBusReply<ProcInfoList> reply = m_netMonitorInter->call(QDBus::CallMode::Block, "GetProcInfoList");
    if (!reply.isValid()) {
        qDebug() << Q_FUNC_INFO << reply.error();
        return ProcInfoList{};
    }
    return reply.value();
}

ProcCleanerModel::MemInfo ProcCleanerModel::getMemInfo() const
{
    MemInfo info;

    QFile f("/proc/meminfo");
    if (!f.open(QIODevice::OpenModeFlag::ReadOnly)) {
        qWarning() << Q_FUNC_INFO << f.fileName() << "open failed";
        return info;
    }

    QString content = f.readAll();
    f.close();

    QStringList contentStrLineList = content.split("\n");
    for (QStringList::const_iterator cIter = contentStrLineList.begin();
         cIter != contentStrLineList.end(); cIter++) {
        if (cIter->startsWith("MemTotal:")) {
            QString totalMemStr = cIter->split(":").last().trimmed();
            totalMemStr.remove("kB");
            info.TotalMem = totalMemStr.toUInt() * KBCount;
        }
        if (cIter->startsWith("MemAvailable:")) {
            QString availableMemStr = cIter->split(":").last().trimmed();
            availableMemStr.remove("kB");
            info.AvailableMem = availableMemStr.toUInt() * KBCount;
        }
    }

    return info;
}

ulong ProcCleanerModel::getProcMem(uint pid) const
{
    const QString filePath = QString("/proc/%1/status").arg(pid);
    QFile f(filePath);
    if (!f.open(QIODevice::OpenModeFlag::ReadOnly)) {
//        qWarning() << Q_FUNC_INFO << filePath << "open failed";
        return 0;
    }

    QString content = f.readAll();
    f.close();

    QStringList contentStrLineList = content.split("\n");
    for (QStringList::const_iterator cIter = contentStrLineList.begin();
         cIter != contentStrLineList.end(); cIter++) {
        if (cIter->startsWith("RssAnon:")) {
            QString memStr = cIter->split(":").last().trimmed();
            memStr.remove(" kB");
            return memStr.toUInt() * KBCount;
        }
    }

    return 0;
}

void ProcCleanerModel::setAppLockState(const QString &pkgName, bool lock)
{
    if (lock) {
        m_memCleanPkgNameWhitelist.append(pkgName);
    } else {
        m_memCleanPkgNameWhitelist.removeOne(pkgName);
    }

    saveCfgs();
}

bool ProcCleanerModel::getAppLockState(const QString &pkgName) const
{
    return m_memCleanPkgNameWhitelist.contains(pkgName);
}

void ProcCleanerModel::asyncKillApps(const QList<AppInfo> &apps)
{
    QList<uint> pidsNeedKill;
    for (QList<AppInfo>::const_iterator cIter = apps.begin();
         cIter != apps.end(); cIter++) {

        // 网易云音乐无法强制关闭，需要特殊处理
        if (NeteaseCloudMusicPkgNameList.contains(cIter->pkgName)) {
            qInfo() << Q_FUNC_INFO << "use specified methoc to kill netease cloud music!";
            QString exeCmd = getExeCmdFromDesktop(cIter->desktopFilePath);
            QProcess proc;
            proc.start("bash", {"-c", exeCmd});
            proc.waitForStarted();
            proc.waitForFinished();
            proc.close();
        }

        pidsNeedKill.append(cIter->pids);
    }

    asyncKillProc(pidsNeedKill);
}

void ProcCleanerModel::setAppViewItemPressing(bool pressing, const QPoint &originPos)
{
    if (pressing) {
        m_isPressingAppViewItem = true;
        m_pressingAppViewItemOriPos = m_pressingAppViewItemPos = originPos;
        m_isMovingAppViewItem = false;
    } else {
        // 判断放下动作
        AppViewItemDroppedAction action;
        int movedYPix = m_pressingAppViewItemPos.y() - m_pressingAppViewItemOriPos.y();
        if (movedYPix < -100) {
            action = Remove;
        } else if (movedYPix > 20) {
            action = Lock;
        } else {
            action = Restore;
        }
        Q_EMIT notifyExecAppViewItemDroppedAction(m_pressingAppInfo, action);

        m_isPressingAppViewItem = false;
        m_pressingAppViewItemOriPos = m_pressingAppViewItemPos = QPoint();
        m_isMovingAppViewItem = false;
    }
}

bool ProcCleanerModel::isAppViewItemPressing()
{
    return m_isPressingAppViewItem;
}

void ProcCleanerModel::setPressingAppViewItemPos(const QPoint &pos)
{
    if (m_isMovingAppViewItem) {
        QPoint adjustPos = pos;
        // 判断下移锁定应用
        if (pos.y() - m_pressingAppViewItemOriPos.y() > 30) {
            adjustPos.setY(m_pressingAppViewItemOriPos.y() + 30);
        }
        m_pressingAppViewItemPos = QPoint(m_pressingAppViewItemOriPos.x(), adjustPos.y());
        Q_EMIT notifyMoveAppViewItem(m_pressingAppViewItemPos);
        return;
    }

    if (qAbs(pos.x() - m_pressingAppViewItemOriPos.x()) > 2 ||
            qAbs(pos.y() - m_pressingAppViewItemOriPos.y()) > 2) {
        m_isMovingAppViewItem = true;
        Q_EMIT notifySetUiAppViewItemVisible(m_pressingAppInfo, false);
        m_pressingAppViewItemPos = QPoint(m_pressingAppViewItemOriPos.x(), pos.y());
        Q_EMIT notifyMoveAppViewItem(m_pressingAppViewItemPos);
    }
}

void ProcCleanerModel::setPressingAppInfo(const AppInfo &info)
{
    m_pressingAppInfo = info;
}

AppInfo ProcCleanerModel::getPressingAppInfo()
{
    return m_pressingAppInfo;
}

void ProcCleanerModel::onKillProcFinished(const QList<uint> &pids)
{
    // 关闭遗留的x窗口
    for (QList<uint>::const_iterator cIter = pids.begin();
         cIter != pids.end(); cIter++) {
        QWindow *w = getXWindowByPid(*cIter);
        if (!w) {
            continue;
        }

        killXWindow(w);
    }

    Q_EMIT killProcFinished(pids);
}
